/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.ipercube.impl.simple;

import com.ipercube.compiler.AbstractPivotCompiler;
import com.ipercube.Column;
import com.ipercube.DataMap;
import com.ipercube.Dimension;
import com.ipercube.Row;
import com.ipercube.model.AxisValue;
import com.ipercube.model.Navigator;
import com.ipercube.model.Node;
import com.ipercube.model.Record;
import com.ipercube.model.Tree;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author tartur
 */
public class SimpleCompiler extends AbstractPivotCompiler {

    public DataMap compile() {
        SimpleDataMap map = new SimpleDataMap();
        for (Record r : getDataModel()) {
            Row row = createRow(r);
            Column col = createColumn(r);
            map.addRow(row, r.getId());
            map.addColumn(col, r.getId());
        }
        return map;
    }

    private Row createRow(Record r) {
        Object[] value = new Object[getRowModel().getMaxLevel() + 1];
        for (int i = 0; i <= getRowModel().getMaxLevel(); i++) {
            value[i] = r.getValue(getRowModel().getDimension(i));
        }
        return new SimpleRow(value);
    }

    private Column createColumn(Record r) {
        Object[] value = new Object[getColModel().getMaxLevel() + 1];
        for (int i = 0; i <= getColModel().getMaxLevel(); i++) {
            value[i] = r.getValue(getColModel().getDimension(i));
        }
        return new SimpleColumn(value);
    }

    /**
     *
     * @author tartur
     */
    private class SimpleDataMap implements DataMap {
        private final Tree<Row,Integer> rowTree;
        private final Tree<Column,Integer> colTree;

        private final AxisValueNode rowRoot = new AxisValueNode(null, new Object(), null);
        private final AxisValueNode colRoot = new AxisValueNode(null, new Object(), null);

        public SimpleDataMap() {
        }

        private void addRow(Row r, Serializable value) {
            for (AxisValueNode node : getOrCreate(rowRoot, r)) {
                node.getValue().add(value);
            }
        }

        private void addColumn(Column c, Serializable value) {
            for (AxisValueNode node : getOrCreate(colRoot, c)) {
                node.getValue().add(value);
            }
        }

        private List<AxisValueNode> getOrCreate(AxisValueNode root, AxisValue axisValue) {
            int parentLevel = root.getDepth();
            List<AxisValueNode> result = new ArrayList();
            AxisValueNode parent = root;
            while (parentLevel < axisValue.getLevel()) {
                parentLevel++;
                AxisValueNode child = parent.getChild(axisValue.getValueByLevel(parentLevel));
                if (child == null) {
                    child = parent.addChild(axisValue.getValueByLevel(parentLevel), new ArrayList());
                }
                result.add(child);
                parent = child;
            }
            return result;
        }

        private List<AxisValueNode> getNodesByLevel(AxisValueNode root, int level) {
            int currentLevel = root.getDepth();
            List<AxisValueNode> result = new ArrayList();
            List<AxisValueNode> parents = new ArrayList();
            // init result
            result.add(root);
              while (currentLevel < level) {
                currentLevel++;
                parents.clear();
                parents.addAll(result);
                result.clear();
                for (AxisValueNode parent : parents) {
                    int childrenNb = parent.getChildren().size();
                    for (int i = 0; i < childrenNb; i++) {
                        result.add(parent.getChild(i));
                    }
                }
            }
            return result;
        }

        private List<Serializable> findValue(AxisValueNode root, AxisValue axisValue) {
            int parentLevel = root.getDepth();
            List<Serializable> result = new ArrayList();
            AxisValueNode parent = root;
            while (parentLevel < axisValue.getLevel()) {
                parentLevel++;
                AxisValueNode child = parent.getChild(axisValue.getValueByLevel(parentLevel));
                if (child == null) {
                    throw new RuntimeException("There is no value for " + axisValue);
                }
                parent = child;
            }
            if (parent == root) {
                throw new RuntimeException("There is no value for " + axisValue);
            }
            result.addAll(parent.getValue());
            return result;
        }

        private Row createRowFromNode(final AxisValueNode node) {
            Object[] value = new Object[node.getDepth() + 1];
            int level = node.getDepth();
            AxisValueNode current = node;
            for (int i = level; i >= 0; i--) {
                value[i] = current.getNode();
                current = current.getParent();
            }
            return new SimpleRow(value);
        }

        private Column createColFromNode(final AxisValueNode node) {
            Object[] value = new Object[node.getDepth() + 1];
            int level = node.getDepth();
            AxisValueNode current = node;
            for (int i = level; i >= 0; i--) {
                value[i] = current.getNode();
                current = current.getParent();
            }
            return new SimpleColumn(value);
        }

        public List<Row> getRowsByLevel(int level) {
            List<Row> rows = new ArrayList<Row>();
            List<AxisValueNode> nodes = getNodesByLevel(rowRoot, level);
            for (AxisValueNode node : nodes) {
                rows.add(createRowFromNode(node));
            }
            return rows;
        }

        public int getRowsLevel() {
            return getRowModel().getMaxLevel();
        }

        public List<Column> getColumnsByLevel(int level) {
            List<Column> columns = new ArrayList<Column>();
            List<AxisValueNode> nodes = getNodesByLevel(colRoot, level);
            for (AxisValueNode node : nodes) {
                columns.add(createColFromNode(node));
            }
            return columns;
        }

        public int getColumnsLevel() {
            return getColModel().getMaxLevel();
        }

        public List<Record> getRecordsAt(Row r, Column c) {
            List<Record> records = new ArrayList();
            List<Serializable> rowRecords = findValue(rowRoot, r);
            List<Serializable> colRecords = findValue(colRoot, c);
            rowRecords.retainAll(colRecords);
            Navigator<Record> nav = getDataModel().getNavigator();
            for (Serializable serializable : rowRecords) {
                Record rec = nav.getById(serializable);
                if (rec != null) {
                    records.add(rec);
                }
            }
            return records;
        }

        public Tree<Row, Integer> getRowsTree() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public Tree<Column, Integer> getColumnsTree() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public class AxisValueNode extends Node<Object, List<Serializable>> {

            public AxisValueNode(AxisValueNode parent, Object node, List<Serializable> value) {
                super(parent,node, value);
            }

            public AxisValueNode getParent() {
                return (AxisValueNode) super.getParent();
            }

            @Override
            protected AxisValueNode createChild(Object node, List<Serializable> value) {
                return new AxisValueNode(this, node, value);
            }

            @Override
            public AxisValueNode addChild(Object child, List<Serializable> value) {
                return (AxisValueNode) super.addChild(child, value);
            }

            @Override
            public AxisValueNode getChild(Object node) {
                return (AxisValueNode) super.getChild(node);
            }

            @Override
            public AxisValueNode getChild(int index) {
                return (AxisValueNode) super.getChild(index);
            }
        }
    }

    private static class Index {

        private Dimension dimension;
        private Map<Object, List<Serializable>> records;

        public Index(Dimension d) {
            dimension = d;
            records = new HashMap();
        }

        public void addRecordId(Object val, Serializable rId) {
            List<Serializable> rec = records.get(val);
            if (rec == null) {
                rec = new ArrayList();
                records.put(val, rec);
            }
            rec.add(rId);
        }

        public List<Serializable> getRecordIds(Object val) {
            return records.get(val);
        }
    }
}
